package top.bulk.encrypt.advice;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import top.bulk.encrypt.annotation.Decrypt;
import top.bulk.encrypt.exception.DecryptBodyFailException;
import top.bulk.encrypt.message.DecryptHttpInputMessage;
import top.bulk.encrypt.strategy.CryptoWrapper;
import top.bulk.encrypt.util.AnnUtil;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/**
 * 解密处理类
 * <p>
 * RequestBodyAdvice 是 Spring Framework 提供的一个接口，它允许开发者在控制器方法接收到请求体数据之前，对请求体的数据进行预处理。
 * <p>
 * 这个接口可以被用来实现诸如请求参数的日志记录、解密、验证签名等功能，尤其在需要对敏感数据进行加密传输的场景下非常有用
 *
 * @author 散装java
 * @date 2024-07-18
 */
@Component
@Slf4j
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 获取参数
        Class<?> clazz = methodParameter.getNestedParameterType();
        // 判断当前返回类型是否被 @Encrypt 注解
        return AnnUtil.hasAnnotation(clazz, Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 对需要解密的参数做解密的操作
        String body;
        try {
            // 读取数据
            body = IoUtil.read(inputMessage.getBody(), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new DecryptBodyFailException("无法获取请求正文数据，请检查发送数据体或请求方法是否符合规范。");
        }
        if (body == null || StrUtil.isEmpty(body)) {
            throw new DecryptBodyFailException("请求正文为NULL或为空字符串，因此解密失败。");
        }

        // 获取入参类型
        Class<?> targetTypeClass;
        try {
            targetTypeClass = Class.forName(targetType.getTypeName());
        } catch (ClassNotFoundException e) {
            throw new DecryptBodyFailException(e.getMessage());
        }

        // 转换成bean，方便操作
        Object obj = JSONUtil.toBean(body, targetTypeClass);

        // 查看是否有解密字段
        Field[] fields = targetTypeClass.getDeclaredFields();
        // 循环处理所有字段
        for (Field field : fields) {
            // 获取注解信息
            Decrypt encrypt = AnnotationUtil.getAnnotation(field, Decrypt.class);
            if (encrypt != null) {
                Object value = ReflectUtil.getFieldValue(obj, field);
                if (value != null) {
                    // 执行加密操作
                    String encryptResult = encrypt.strategy().decrypt(new CryptoWrapper().setFieldValue(value.toString()).setArguments(encrypt.arguments()));
                    // 通过反射更新数值
                    ReflectUtil.setFieldValue(obj, field, encryptResult);
                }
            }
        }
        String decryptBody = JSONUtil.toJsonStr(obj);
        if (decryptBody == null) {
            throw new DecryptBodyFailException("解密错误，请检查选择的源数据的加密方式是否正确。");
        }
        try {
            // 将解密后的参数对象，写入到 HttpInputMessage 中去
            return new DecryptHttpInputMessage(IoUtil.toStream(decryptBody, StandardCharsets.UTF_8), inputMessage.getHeaders());
        } catch (Exception e) {
            throw new DecryptBodyFailException("字符串转换成流格式异常，请检查编码等格式是否正确");
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
